﻿// <copyright file="AccountController.cs" company="Ecole Normale Supérieure">
// Released under GPLv2
// </copyright>
// <summary>Contains the account controller</summary>

namespace Sqwarea.Web.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.Linq;
    using System.Security.Principal;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Security;
    using System.Web.UI;
    using DotNetOpenAuth.Messaging;
    using DotNetOpenAuth.OpenId;
    using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
    using DotNetOpenAuth.OpenId.RelyingParty;
    using Sqwarea.GameLogic;
    using Sqwarea.Persistence;
    using Sqwarea.Web.Extensions;
    using Sqwarea.Web.Misc;

    /// <summary>
    /// Account controller
    /// </summary>
    [HandleError]
    public class AccountController : SecuredController
    {
        /// <summary>
        /// The user starter for registering purposes
        /// </summary>
        private readonly IUserStarter _userStarter;

        /// <summary>
        /// The game provider allow to get king from user (used for account settings update)
        /// </summary>
        private readonly IGameProvider _gameProvider;

        /// <summary>
        /// The relying party, responsible of the whole authentication process
        /// </summary>
        private static OpenIdRelyingParty _relyingParty = new OpenIdRelyingParty();

        /// <summary>
        /// Initializes a new instance of the AccountController class
        /// </summary>
        /// <param name="userProvider">The user provider</param>
        /// <param name="userStarter">The user starter</param>
        /// <param name="gameProvider">The game provider</param>
        public AccountController(IUserProvider userProvider, IUserStarter userStarter, IGameProvider gameProvider)
            : base(userProvider)
        {
            if (userStarter == null)
            {
                throw new ArgumentNullException("userStarter", "userStarter must not be null");
            }

            if (gameProvider == null)
            {
                throw new ArgumentNullException("gameProvider", "gameProvider must not be null");
            }

            this._userStarter = userStarter;
            this._gameProvider = gameProvider;
        }

        /// <summary>
        /// Gets supported languages list
        /// </summary>
        private static IEnumerable<SelectListItem> SupportedLanguages
        {
            get
            {
                var items = new List<SelectListItem>();
                var english = new SelectListItem();
                english.Text = "English";
                english.Value = "en";
                items.Add(english);
                return items;
            }
        }

        /// <summary>
        /// Action allowing user to register
        /// </summary>
        /// <returns>The action result</returns>
        public ActionResult Register()
        {
            var user = (User) Session["User"];
            if (user == null)
            {
                return RedirectToAction("LogOn");
            }

            ViewData["Messages"] = new List<string>();

            ViewData["Nickname"] = user.Nickname;
            ViewData["Email"] = user.Email;
            ViewData["Language"] = user.Language;

            ViewData["Languages"] = AccountController.SupportedLanguages;

            return View();
        }

        /// <summary>
        /// Action allowing user to register
        /// </summary>
        /// <param name="nickname">User's nickname</param>
        /// <returns>The action result</returns>
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Register(string nickname)
        {
            var user = (User)Session["User"];
            user.Nickname = Request.Form["nickname"];
            user.Email = Request.Form["email"];
            user.Language = Request.Form["language"];

            ViewData["Messages"] = new List<string>();

            this.CheckCaptcha(
                Request.Form["recaptcha_challenge_field"],
                Request.Form["recaptcha_response_field"]);
            this.CheckNickname(user.Nickname);
            this.CheckLanguage(user.Language);
            this.CheckEmail(user.Email);

            if (((List<string>)ViewData["Messages"]).Count > 0)
            {
                ViewData["Languages"] = AccountController.SupportedLanguages;
                Session["User"] = user;
                ViewData["Nickname"] = user.Nickname;
                ViewData["Email"] = user.Email;
                ViewData["Language"] = user.Language;
                return View();
            }

            Session.Remove("User");
            this._userStarter.CreateNewUser(user);

            return RedirectToAction("Index", "Game");
        }

        /// <summary>
        /// Action allowing user to log on
        /// </summary>
        /// <returns>The action result</returns>
        [Unrestricted]
        public ActionResult LogOn()
        {
            string openId = Request.Form["openId"];
            bool rememberMe = (Request.Form["rememberMe"] != null)
                    && Request.Form["rememberMe"].Contains("true");

            ViewData["RememberMe"] = rememberMe;

            var response = _relyingParty.GetResponse();
            if (response != null)
            {
                var form = (NameValueCollection) Session["LogOnForm"];
                Session.Remove("LogOnForm");
                openId = response.ClaimedIdentifier;
                rememberMe = form["rememberMe"].Contains("true");
                
                // Stage 3: OpenID Provider sending assertion response
                switch (response.Status)
                {
                    case AuthenticationStatus.Authenticated:
                        var exists = this.UserProvider.UserExists(openId);
                        User user = null;
                        if (exists)
                        {
                            user = this.UserProvider.GetUserByOpenId(openId);
                        }
                        else
                        {
                            user = new User();
                            user.Initialize();
                            user.OpenId = openId;
                        }

                        var extension = response.GetExtension<ClaimsResponse>();

                        if (!exists || user.Id < 0)
                        {
                            if (extension != null)
                            {
                                user.Nickname = extension.Nickname;
                                user.Email = extension.Email;
                            }

                            user.Language = "en";
                            Session["User"] = user;
                        }
                    
                        user.LastConnection = DateTime.Now;

                        if (rememberMe)
                        {
                            string key = AccountController.GenerateRandomKey(100);
                            HttpCookie openIdCookie = new HttpCookie("OpenId", openId);
                            openIdCookie.Expires = DateTime.Now.AddDays(30);
                            Response.Cookies.Add(openIdCookie);
                            HttpCookie rememberMeCookie = new HttpCookie("RememberMeKey", key);
                            rememberMeCookie.Expires = DateTime.Now.AddDays(30);
                            Response.Cookies.Add(rememberMeCookie);
                            user.RememberMe(key);
                        }
                        else
                        {
                            var cookie = Request.Cookies.Get("RememberMe");
                            if (cookie != null)
                            {
                                user.RemembranceSessions.Remove(cookie.Value);
                            }

                            Response.Cookies.Remove("OpenId");
                            Response.Cookies.Remove("RememberMeKey");
                        }

                        this.UserProvider.UpdateUser(user);

                        Session.Add("OpenId", openId);

                        return RedirectToAction("Index", "Game");

                    case AuthenticationStatus.Canceled:
                        ViewData["Message"] = "Canceled at provider";
                        return View();
                    case AuthenticationStatus.Failed:
                        ViewData["Message"] = response.Exception.Message;
                        return View();
                }

                return View();
            }

            if (openId == null || openId.Length == 0)
            {
                return View();
            }            

            // Stage 2: user submitting Identifier
            Identifier id;
            if (Identifier.TryParse(openId, out id))
            {
                try
                {
                    Session["LogOnForm"] = Request.Form;
                    ViewData["Message"] = Request.Form["rememberMe"];
                    var request = _relyingParty.CreateRequest(openId);
                    request.AddExtension(new ClaimsRequest
                    {
                        Nickname = DemandLevel.Request,
                        Email = DemandLevel.Request,
                        Language = DemandLevel.Request
                    });

                    request.RedirectToProvider();
                }
                catch (ProtocolException)
                {
                    ViewData["Message"] = "No such endpoint can be found.";
                }

                return View();
            }
            else
            {
                ViewData["Message"] = "Invalid identifier";
                return View();
            }
        }

        /// <summary>
        /// Action allowing user to manage its account
        /// </summary>
        /// <returns>The action result</returns>
        public ActionResult Settings()
        {
            var user = this.User;
            if (user == null)
            {
                return RedirectToAction("LogOn");
            }

            ViewData["User"] = user;
            ViewData["Messages"] = new List<string>();

            ViewData["Nickname"] = user.Nickname;
            ViewData["Email"] = user.Email;
            ViewData["Language"] = user.Language;

            ViewData["Languages"] = AccountController.SupportedLanguages;

            return View();
        }

        /// <summary>
        /// Action allowing user to manage its account
        /// </summary>
        /// <param name="email">User's email</param>
        /// <returns>The action result</returns>
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Settings(string email)
        {
            var user = this.User;
            user.Email = Request.Form["email"];
            user.Language = Request.Form["language"];
            user.Nickname = Request.Form["nickname"];

            ViewData["User"] = user;
            ViewData["Messages"] = new List<string>();

            this.CheckNickname(user.Nickname);
            this.CheckEmail(user.Email);
            this.CheckLanguage(user.Language);

            ViewData["Nickname"] = user.Nickname;
            ViewData["Email"] = user.Email;
            ViewData["Language"] = user.Language;

            ViewData["Languages"] = AccountController.SupportedLanguages;

            if (((List<string>)ViewData["Messages"]).Count == 0)
            {
                UserProvider.UpdateUser(user);
                var king = this._gameProvider.GetKingById(user.Id);
                king.Nickname = user.Nickname;
                this._gameProvider.UpdateKing(king);
                ((List<string>)ViewData["Messages"]).Add("Account informations successfully updated!");
            }

            return View();
        }

        /// <summary>
        /// Action allowing user to delete its account
        /// </summary>
        /// <returns>The action result</returns>
        public ActionResult Delete()
        {
            ViewData["User"] = this.User;

            ViewData["Nickname"] = this.User.Nickname;

            return View();
        }

        /// <summary>
        /// Action allowing user to manage its account
        /// </summary>
        /// <param name="confirmdelete">Account deletion confirmation</param>
        /// <returns>The action result</returns>
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(string confirmdelete)
        {
            var confirm = Request.Form["confirm"];
            var user = this.User;

            if (confirm == "yes")
            {
                var king = this._gameProvider.GetKingById(user.Id);
                king.OpenId = null;
                this._gameProvider.UpdateKing(king);

                var kingSquare = this._gameProvider.GetSquareByPosition(king.Position);
                kingSquare.HasKing = false;
                this._gameProvider.UpdateSquare(kingSquare);

                UserProvider.DeleteUser(user);

                return RedirectToAction("LogOff", "Account");
            }

            return RedirectToAction("Settings", "Account");
        }

        /// <summary>
        /// Action allowing user to change its OpenId
        /// </summary>
        /// <returns>The action result</returns>
        [Unrestricted]
        public ActionResult ChangeOpenId()
        {
            string openId = Request.Form["openId"];

            ViewData["User"] = this.User;

            ViewData["Messages"] = new List<string>();

            var response = _relyingParty.GetResponse();
            if (response != null)
            {
                Session.Remove("LogOnForm");
                openId = response.ClaimedIdentifier;

                // Stage 3: OpenID Provider sending assertion response
                switch (response.Status)
                {
                    case AuthenticationStatus.Authenticated:
                        var exists = this.UserProvider.UserExists(openId);
                        var user = this.User;
                        long uid = 0;
                        if (exists)
                        {
                            uid = this.UserProvider.GetUserByOpenId(openId).Id;
                        }

                        if (exists && uid > 0)
                        {
                            ((List<string>)ViewData["Messages"]).Add("This OpenID is already taken by another account. If you have two account you may want to delete one.");
                        }
                        else
                        {
                            UserProvider.DeleteUser(user);
                            user.OpenId = openId;
                            UserProvider.UpdateUser(user);
                            var king = this._gameProvider.GetKingById(user.Id);
                            king.OpenId = user.OpenId;
                            this._gameProvider.UpdateKing(king);
                            ViewData["OpenId"] = openId;
                            ((List<string>)ViewData["Messages"]).Add("Your OpenID has successfully been updated.");
                            Session.Remove("OpenId");
                            Session.Add("OpenId", openId);
                        }

                        break;

                    case AuthenticationStatus.Canceled:
                        ((List<string>)ViewData["Messages"]).Add("Your OpenID hasn't been updated.");
                        break;

                    case AuthenticationStatus.Failed:
                        ((List<string>)ViewData["Messages"]).Add(response.Exception.Message);
                        break;
                }

                return View();
            }

            if (openId == null || openId.Length == 0)
            {
                ViewData["OpenId"] = this.User.OpenId;
                return View();
            }

            // Stage 2: user submitting Identifier
            Identifier id;
            if (Identifier.TryParse(openId, out id))
            {
                try
                {
                    Session["LogOnForm"] = Request.Form;
                    var request = _relyingParty.CreateRequest(openId);
                    request.RedirectToProvider();
                }
                catch (ProtocolException)
                {
                    ((List<string>)ViewData["Messages"]).Add("No such endpoint can be found.");
                }

                return View();
            }
            else
            {
                ((List<string>)ViewData["Messages"]).Add("Invalid identifier.");
                return View();
            }
        }

        /// <summary>
        /// Action allowing user to log off
        /// </summary>
        /// <returns>The action result</returns>
        public ActionResult LogOff()
        {
            var cookie = Request.Cookies.Get("RememberMe");
            if (cookie != null)
            {
                User.RemembranceSessions.Remove(cookie.Value);
            }

            Session.Remove("OpenId");
            Response.Cookies["OpenId"].Expires = DateTime.Now.AddDays(-1);
            Response.Cookies["RememberMeKey"].Expires = DateTime.Now.AddDays(-1);
            this.UserProvider.UpdateUser(User);
            OpenIdRelyingPartyControlBase.LogOff();
            return RedirectToAction("Index", "Home");
        }

        /// <summary>
        /// Generates a random string with the given length
        /// </summary>
        /// <param name="size">Size of the string</param>
        /// <returns>Random string</returns>
        private static string GenerateRandomKey(int size)
        {
            StringBuilder builder = new StringBuilder();
            Random random = new Random();
            char ch;
            for (int i = 0; i < size; i++)
            {
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor((26 * random.NextDouble()) + 65)));
                builder.Append(ch);
            }

            return builder.ToString();
        }

        /// <summary>
        /// Check wether or not nickname is valid
        /// </summary>
        /// <param name="nickname">The nickname to check</param>
        /// <returns>Nickname validity</returns>
        private bool CheckNickname(string nickname)
        {
            if (nickname == null || nickname.Length == 0)
            {
                ((List<string>)ViewData["Messages"]).Add("Nickname required");
                return false;
            }

            if (nickname.Length < 4)
            {
                ((List<string>)ViewData["Messages"]).Add("Nickname too short (> 3)");
                return false;
            }

            if (nickname.Length > 50)
            {
                ((List<string>)ViewData["Messages"]).Add("Nickname too long (< 50)");
                return false;
            }

            Match match = Regex.Match(nickname, "^[a-zA-Z_ 0-9-\\.]*$");

            if (!match.Success)
            {
                ((List<string>)ViewData["Messages"]).Add("Invalid nickname (only letters and numbers are authorized)");
                return false;
            }

            return true;
        }

        /// <summary>
        /// Check wether or not language is valid
        /// </summary>
        /// <param name="language">The language to check</param>
        /// <returns>Language validity</returns>
        private bool CheckLanguage(string language)
        {
            if (language != "en")
            {
                ((List<string>)ViewData["Messages"]).Add("Language not supported");
                return false;
            }

            return true;
        }

        /// <summary>
        /// Check email validity
        /// </summary>
        /// <param name="email">The email to check</param>
        /// <returns>Email validity</returns>
        private bool CheckEmail(string email)
        {
            if (email == null || email.Length == 0)
            {
                ((List<string>)ViewData["Messages"]).Add("Email required");
                return false;
            }

            Match match = Regex.Match(email, "^[^@]+@[^@]+\\..+$");

            if (!match.Success)
            {
                ((List<string>)ViewData["Messages"]).Add("Invalid email");
                return false;
            }

            return true;
        }

        /// <summary>
        /// Check captcha validity
        /// </summary>
        /// <param name="challenge">reCAPTCHA challenge field</param>
        /// <param name="response">reCAPTCHA response field</param>
        /// <returns>captach validity</returns>
        private bool CheckCaptcha(string challenge, string response)
        {
            var captchaValidtor = new Recaptcha.RecaptchaValidator
                                      {
                                          PrivateKey = "6LdLgQwAAAAAADcr5ptAGOUpoTNlE4OQHN07BqwK ",
                                          RemoteIP = Request.UserHostAddress,
                                          Challenge = challenge,
                                          Response = response
                                      };
 
            if (!captchaValidtor.Validate().IsValid)
            {
                ((List<string>)ViewData["Messages"]).Add("Human/seahorse/kiwi verification failed.");
                return false;
            }

            return true;
        }
    }
}
